#include "libnstr.h"

nstr_t  *nstr_make()
{
  nstr_t *nstr;

  nstr = (nstr_t*)malloc(sizeof(nstr_t));
  nstr->data = (char*)malloc(sizeof(char) * NSTR_INITBUFFSIZE);
  nstr->size = NSTR_INITBUFFSIZE;
  nstr->data = memset(nstr->data, 0, nstr->size);
  nstr->write = 0;
  return nstr;
}

void    nstr_dispose(nstr_t *nstr)
{
  free(nstr->data);
  free(nstr);
}

nstr_t  *nstr_clean(nstr_t *nstr)
{
  nstr->data = memset(nstr->data, 0, nstr->size);
  nstr->write = 0;
  return nstr;
}

nstr_t  *nstr_sizeup(nstr_t *nstr)
{
  int   new_size;

  new_size = nstr->size * 2;
  nstr->data = (char*)realloc(nstr->data, sizeof(char) * new_size);
  nstr->size = new_size;
  return nstr;
}

nstr_t  *nstr_reserve(nstr_t *nstr, int size)
{
  for(; (nstr->size - nstr->write) < size; )
    nstr = nstr_sizeup(nstr);
  return nstr;
}

int     nstr_fill_with_file(nstr_t *nstr, const char *path)
{

  return 0;
}

int     nstr_read(nstr_t *nstr, int fd, int size)
{
  return 0;
}

nstr_t  *nstr_addnstr(nstr_t *nstr1, nstr_t *nstr2)
{
  nstr1 = nstr_reserve(nstr1, nstr2->write + 1);
  memcpy((nstr1->data + nstr1->write)
         , nstr2->data, nstr2->write);

  nstr1->write += nstr2->write;
  nstr1->data[nstr1->write] = '\0';
  return nstr1;
}

nstr_t  *nstr_addstr(nstr_t *nstr, const char *str)
{
  int   size;

  size = strlen(str);
  nstr = nstr_reserve(nstr, size + 1);
  memcpy((nstr->data + nstr->write)
         , str, size);
  nstr->write += size;
  nstr->data[nstr->write + size] = '\0';
  return nstr;
}

nstr_t  *nstr_addstr_n(nstr_t *nstr, const char *str, int n)
{
  nstr = nstr_reserve(nstr, n + 1);
  memcpy((nstr->data + nstr->write)
         , str, n);
  nstr->write += n;
  nstr->data[nstr->write] = '\0';

  return nstr;
}

nstr_t  *nstr_addchar(nstr_t *nstr, char c)
{
  nstr = nstr_reserve(nstr, 1);
  nstr->data[nstr->write] = c;
  nstr->write += 1;
  nstr->data[nstr->write] = '\0';
  return nstr;
}

nstr_t  *nstr_addint(nstr_t *nstr, int i)
{
  char  buff[20];

  sprintf(buff, "%i", i);
  return nstr_addstr(nstr, buff);
}

nstr_t  *nstr_addlong(nstr_t *nstr, long l)
{
  char  buff[20];

  sprintf(buff, "%i", (int)l);
  return nstr_addstr(nstr, buff);
}

nstr_t  *nstr_add(nstr_t *nstr, const char *str, ...)
{
  va_list argp;

  int   i, j;
  char  *s;
  char  c;
  long  l;
  nstr_t *n;

  va_start(argp, str);

  for(j = 0; str[j] != '\0'; j++)
    {
      if(str[j] != '%')
        nstr = nstr_addchar(nstr, str[j]);
      if(str[j] == '%')
        {
          if(str[j + 1] != '\0')
            j++;
          switch(str[j])
            {
            case 'c':
              c = va_arg(argp, int);
              nstr = nstr_addchar(nstr, c);
              break;
            case 'd':
              i = va_arg(argp, int);
              nstr = nstr_addint(nstr, i);
              break;
            case 'i':
              i = va_arg(argp, int);
              nstr = nstr_addint(nstr, i);
              break;
            case 's':
              s = va_arg(argp, char *);
              nstr = nstr_addstr(nstr, s);
              break;
            case 'n':
              n = va_arg(argp, nstr_t *);
              nstr = nstr_addnstr(nstr, n);
              break;
            case 'l':
              l = va_arg(argp, long);
              nstr = nstr_addlong(nstr, l);
              break;
            case '%':
              nstr = nstr_addchar(nstr, '%');
              break;
            }
        }
    }
  va_end(argp);
  return nstr;
}


  // emample : nstr_replace(mynstr, "cinlsd", mychar
  // , myint, mylong, mystr, mynum);
  // will remplace all %0%, %1%... with the value in the order
nstr_t  *nstr_replace(nstr_t *nstr, const char *str, ...)
{
  nstr_t *tmp_nstr;
  int   i, j;
  nstrt_t *optlist;
  va_list argp;
  char  c;
  char  *s;
  nstr_t *n;
  long  l;
  int   flg = 0;
  char  buff[9];
  int   element_find;
  char  tmp;

  optlist = nstrt_make();
  va_start(argp, str);
  for(i = 0; str[i] != '\0'; i++)
    {
      tmp_nstr = nstr_make();
      tmp = str[i];
      switch(tmp)
        {
        case 'c':
          c = va_arg(argp, int);
          tmp_nstr = nstr_addchar(tmp_nstr, c);
          break;
        case 'd':
          j = va_arg(argp, int);
          tmp_nstr = nstr_addint(tmp_nstr, j);
          break;
        case 'i':
          j = va_arg(argp, int);
          tmp_nstr = nstr_addint(tmp_nstr, j);
          break;
        case 's':
          s = va_arg(argp, char *);
          tmp_nstr = nstr_addstr(tmp_nstr, s);
          break;
        case 'n':
          n = va_arg(argp, nstr_t *);
          tmp_nstr = nstr_addnstr(tmp_nstr, n);
          break;
        case 'l':
          l = va_arg(argp, long);
          tmp_nstr = nstr_addlong(tmp_nstr, l);
          break;
        default:
          nstr_dispose(tmp_nstr);
          flg = 1;
          break;
        }
      if(flg == 1)
        flg = 0;
      else
        {
          optlist = nstrt_addend(optlist, tmp_nstr);
        }
    }
  va_end(argp);

  tmp_nstr = nstr_make();
  nstr_addnstr(tmp_nstr, nstr);
  nstr_clean(nstr);
  for(i = 0; i < tmp_nstr->write; i++)
    {
      if(tmp_nstr->data[i] == '%')
        {
          i++;
          for(j = 0; tmp_nstr->data[i + j] != '%'
                && j < 8
                && tmp_nstr->data[i + j] - 48 < 10
                && tmp_nstr->data[i + j] - 48 >= 0
                ; j++)
            buff[j] = tmp_nstr->data[i + j];
          buff[j] = '\0';
          if(j == 8)
            // error 1
            ;
          if(tmp_nstr->data[i + j] - 48 > 10
             || tmp_nstr->data[i + j] - 48 < 0)
            // error 1
            ;
          if(tmp_nstr->data[i + j] == '%')
            {
              element_find = atoi(buff);
              if(nstrt_getelem(optlist, element_find) == NULL)
                // error not find
                ;
              else
                {
                  i += j;
                  nstr_addnstr(nstr, nstrt_getelem(optlist, element_find));
                }
            }
        }
      else
        nstr_addchar(nstr, tmp_nstr->data[i]);
    }
  return nstr;
}

// return a cursor to the end of the n occurence of the pattern
// find in the string
// if nothing is found nstr->write is return. n must be at least 1
int     nstr_find_pos_after(nstr_t *nstr, const char *pattern
                            , int n, int start_from)
{
  int   i;
  int   j;
  int   occurence;

  occurence = 0;
  i = start_from;

 again:

  for(; i < nstr->write; i++)
    {
      for(j = 0; pattern[j] == nstr->data[i + j] && pattern[j] != '\0'; j++)
        ;
      if(pattern[j] == '\0')
        {
          occurence++;
          if(occurence == n)
            return (i + j);
          else
            {
              i = i + j;
              goto again;
            }
        }
    }
  return  nstr->write;
}

// return a cursor to the begin of the n occurence of the pattern
// find in the string
// if nothing is found nstr->write is return. n must be at least 1
int     nstr_find_pos_begin(nstr_t *nstr, const char *pattern
                            , int n, int start_from)
{
  int   i;
  int   j;
  int   occurence;

  occurence = 0;
  i = start_from;

 again:
  
  for(; i < nstr->write; i++)
    {
      for(j = 0; pattern[j] == nstr->data[i + j] && pattern[j] != '\0'; j++)
        ;
      if(pattern[j] == '\0')
        {
          occurence++;
          if(occurence == n)
            return (i);
          else
            {
              i = i + j;
              goto again;
            }
        }
    }
  return nstr->write;
}

// fill result with the phrase found after the n occurence of
// pattern and before the end pattern, if nothing is found
// result is left untouched. n must be at least 1
nstr_t  *nstr_get_pattern(nstr_t *data, nstr_t *result
                          , const char *pattern_begin
                          , const char *pattern_end
                          , int n)
{
  int   begin;
  int   end;

  begin = nstr_find_pos_after(data, pattern_begin, n, 0);
  if(begin == data->write)
    return result;
  // we take the first result from the begin position
  end = nstr_find_pos_begin(data, pattern_end, 1, begin);
  if(end == begin)
    return result;
  else if(end == data->write)
    return result;
  else
    result = nstr_addstr_n(result, data->data + begin, end - begin);
  return result;
}

nstrt_t *nstr_split(nstrt_t *table, nstr_t *nstr, const char *pattern)
{
  nstr_t *tmp;
  int   begin;
  int   end;
  
  for(begin = 0, end = 0; end < nstr->write
        && begin < nstr->write;)
    {
      end = nstr_find_pos_begin(nstr, pattern, 1, begin);
      if(end != begin)
        {
          tmp = nstr_make();
          tmp = nstr_addstr_n(tmp, nstr->data + begin, end - begin);
          begin = end + strlen(pattern);
          table = nstrt_addend(table, tmp);
        }
      else
        begin++;
    }
  return table;
}

// send all the string by chunks of packet_size
int     nstr_send(nstr_t *nstr, int socket, int packet_size)
{
  int   i;
  int   n;

  for(i = 0, n = 0; i < nstr->write; )
    {
      if(nstr->write - i > packet_size)
        {
          n = __nstr_writen(socket, nstr->data + i, packet_size);
          if(n < 0)
            return n;
        }
      if(nstr->write - i < packet_size && nstr->write > i)
        n = __nstr_writen(socket, nstr->data + i, nstr->write - i);
      i += n;
    }
  return i;
}

int     nstr_error(int error_id, int error_code)
{
  return 0;
}

nstrt_t *nstrt_make()
{
  nstrt_t *nstrt;

  nstrt = (nstrt_t*)malloc(sizeof(nstrt_t));
  nstrt->table = (nstr_t**)malloc(sizeof(nstr_t*) * NSTR_INITTABLESIZE);
  nstrt->size = NSTR_INITTABLESIZE;
  memset(nstrt->table, 0, nstrt->size);
  nstrt->elements = 0;
  return nstrt;
}

// will also delete all nstr in table
void    nstrt_dispose_all(nstrt_t *nstrt)
{
  int   i;

  for(i = 0; i < nstrt->elements; i++)
    free(nstrt->table[i]);
  nstrt_dispose(nstrt);
}

// dont free the data in the table
void    nstrt_dispose(nstrt_t *nstrt)
{
  free(nstrt->table);
  free(nstrt);
}

nstrt_t *nstrt_sizeup(nstrt_t *nstrt)
{
  int   new_size;

  new_size = nstrt->size * 2;
  nstrt->table = (nstr_t**)realloc(nstrt->table, sizeof(nstr_t*) * new_size);
  nstrt->size = new_size;
  return nstrt;
}

nstrt_t *nstrt_addend(nstrt_t *nstrt, nstr_t *element)
{
  if(nstrt->elements + 1 >= nstrt->size)
    nstrt = nstrt_sizeup(nstrt);
  nstrt->table[nstrt->elements] = element;
  nstrt->elements++;
  nstrt->table[nstrt->elements] = '\0';
  return nstrt;
}

void    nstrt_addstrend(nstrt_t *nstrt, const char *str)
{
  nstr_t *nstr;

  nstr = nstr_make();
  nstr_addstr(nstr, str);
  nstrt_addend(nstrt, nstr);
}

void    nstrt_removelast(nstrt_t *nstrt, nstr_t *element)
{
  if(nstrt->elements == 0)
    return;
  nstrt->elements--;
  nstrt->table[nstrt->elements] = '\0';
}

void    nstrt_deletelast(nstrt_t *nstrt)
{
  if(nstrt->elements == 0)
    return;
  nstrt->elements--;
  free(nstrt->table[nstrt->elements]);
  nstrt->table[nstrt->elements] = '\0';
}

nstr_t *nstrt_getelem(nstrt_t *nstrt, int element)
{
  if(element < nstrt->elements)
    return nstrt->table[element];
  else
    return NULL;
}

// return a pointer to nstr filled with the or,
// return -1 if file not found
// rerurn -2 if not a regular file
// return -3 if access denied
// return -4 if read error
nstr_t  *nstr_file_read_all(nstr_t *nstr, const char *path)
{
  nstrfile_t *file;

  file = nstr_open_file(path);
  if(file < 0)
    return (nstr_t*)file;
  nstr = nstr_reserve(nstr, file->size);

  if(fread (nstr->data + nstr->write
            , 1
            , file->size, file->stream)
     != file->size)
    return (nstr_t*)-4;
  fclose (file->stream);
  nstr->write += file->size;

  free(file);
  return nstr;
}


// return a pointer to an open file, else
// return -1 on error
nstrfile_t *nstr_open_file(const char *path)
{
  nstrfile_t *new_file;

  // means its ok
  new_file = (nstrfile_t*)malloc(sizeof(nstrfile_t));
  new_file->stream = fopen(path , "r" );
  // one check
  if(new_file->stream == NULL)
    {
      free(new_file);
      return((nstrfile_t*)-1);
    }
  // get size of the file
  fseek(new_file->stream , 0 , SEEK_END);
  new_file->size = ftell(new_file->stream);
  rewind (new_file->stream);
  // nothing read yet
  new_file->read = 0;

  return new_file;
}



ssize_t __nstr_writen(int fd, const void *vptr, size_t n)
{
  size_t nleft;
  ssize_t nwritten;
  const char *ptr;

  ptr = vptr;
  nleft = n;
  while (nleft > 0) {
    if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
      if (nwritten < 0 && errno == EINTR)
        nwritten = 0;		/* and call write() again */
      else
        return(-1);			/* error */
    }

    nleft -= nwritten;
    ptr   += nwritten;
  }
  return(n);
}
